package com.hra.logger;

import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import android.util.SparseArray;

import com.hra.logger.collector.BaseLogCollector;
import com.hra.logger.utils.LogFactory;
import com.hra.logger.utils.Utils;

import java.io.File;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

public class LoggingService extends Service {
    private static final String TAG = "LoggingService";

    private static final Object cleanupLock = new Object();
    private static boolean mProcessingFlag = false;
    private static final int SAMPLING_FRAME_COUNT = 30;
    private static int mCount = 0;

    private static final String CHANNEL_ID = "LoggerServiceChannel";
    private static final String CHANNEL_NAME = "Logger Service Channel";
    private static final int NOTIFICATION_ID = 101;

    private final IBinder binder = new LocalBinder();
    private final SparseArray<BaseLogCollector> activeCollectors = new SparseArray<>();

    private SharedPreferences sharedPreferences = null;
    private TimerCallback mTimerCallback = null;
    private Timer mTimer;
    private long mTimeSecond = 0;

    private final BroadcastReceiver shutdownReceiver = new BroadcastReceiver() {
        @SuppressLint("ApplySharedPref")
        @Override
        public void onReceive(Context context, Intent intent) {
            if (sharedPreferences != null && activeCollectors.size() > 0) {
                sharedPreferences.edit().putLong(Utils.KEY_LAST_RECORD_TIME, System.currentTimeMillis()).commit();
            }
        }
    };

    private final BroadcastReceiver storageReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            stopAllLogging();
            Log.w(TAG, "onReceive: ACTION_DEVICE_STORAGE_LOW, stopAllLogging !");
        }
    };

    public class LocalBinder extends Binder {
        LoggingService getService() {
            return LoggingService.this;
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        if (Utils.DEBUG) {
            Log.i(TAG, "Service onCreate");
        }
        createNotificationChannel();
        startForeground(NOTIFICATION_ID, createNotification());
        sharedPreferences = getSharedPreferences(Utils.PRE_STATE_NAME, Context.MODE_PRIVATE);
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SHUTDOWN);
        filter.addAction(Intent.ACTION_REBOOT);
        registerReceiver(shutdownReceiver, filter);

        IntentFilter storageFilter = new IntentFilter();
        storageFilter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
        registerReceiver(storageReceiver, storageFilter);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (Utils.DEBUG) {
            Log.i(TAG, "onStartCommand received");
        }

        if (intent != null) {
            // Processing requests for startup logs after reboot
            if (Utils.ACTION_START_LOGGING.equals(intent.getAction())) {
                int logType = intent.getIntExtra(Utils.KEY_LOG_TYPE, 0);
                if (logType != 0) {
                    startLoggingAfterBoot(logType);
                }
            }
        }

        return START_STICKY;
    }

    /**
     * Start log collection after reboot (special handling)
     */
    private void startLoggingAfterBoot(int logType) {
        if (isLoggingActive(logType)) {
            Log.w(TAG, "Logging already active for: " + Utils.getLogTypeName(logType));
            return;
        }

        BaseLogCollector collector = LogFactory.getLogCollector(logType);
        if (collector != null) {
            collector.setFirstStartAfterReboot(Utils.USE_OLD_DIR_AFTER_REBOOT);

            collector.start();
            activeCollectors.put(logType, collector);

            if (shouldStartTimer()) {
                startTimer();
            }

            updateNotification();
            Log.i(TAG, "Started logging after boot for: " + Utils.getLogTypeName(logType));
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

    @SuppressLint("ApplySharedPref")
    public void startLogging(int logType) {
        if (isLoggingActive(logType)) {
            Log.w(TAG, "Logging already active for: " + Utils.getLogTypeName(logType));
            return;
        }

        if (sharedPreferences != null) {
            sharedPreferences.edit().putBoolean(Utils.getLogTypeName(logType), true).commit();
        }

        BaseLogCollector collector = LogFactory.getLogCollector(logType);
        if (collector != null) {
            // Clear the restart flag during normal startup
            collector.setFirstStartAfterReboot(false);

            collector.start();
            activeCollectors.put(logType, collector);

            if (shouldStartTimer()) {
                startTimer();
            }

            updateNotification();
            Log.i(TAG, "Started logging for: " + Utils.getLogTypeName(logType));
        }
    }

    @SuppressLint("ApplySharedPref")
    public void stopLogging(int logType) {
        if (sharedPreferences != null) {
            sharedPreferences.edit().putBoolean(Utils.getLogTypeName(logType), false).commit();
        }
        BaseLogCollector collector = activeCollectors.get(logType);
        if (collector != null) {
            collector.stop();
            activeCollectors.remove(logType);

            if (shouldStopTimer()) {
                stopTimer();
            }

            updateNotification();
            Log.i(TAG, "Stopped logging for: " + Utils.getLogTypeName(logType));
        }
    }

    @SuppressLint("ApplySharedPref")
    public void stopAllLogging() {
        if (sharedPreferences != null) {
            for (int i = 0; i < activeCollectors.size(); i++) {
                int logType = activeCollectors.keyAt(i);
                sharedPreferences.edit().putBoolean(Utils.getLogTypeName(logType), false).commit();
            }
        }
        for (int i = 0; i < activeCollectors.size(); i++) {
            BaseLogCollector collector = activeCollectors.valueAt(i);
            collector.stop();
        }
        activeCollectors.clear();

        stopTimer();

        updateNotification();
        Log.i(TAG, "Stopped all logging");
    }

    public boolean isLoggingActive(int logType) {
        return activeCollectors.get(logType) != null;
    }

    private void createNotificationChannel() {
        NotificationChannel channel = new NotificationChannel(
                CHANNEL_ID,
                CHANNEL_NAME,
                NotificationManager.IMPORTANCE_LOW
        );
        NotificationManager manager = getSystemService(NotificationManager.class);
        if (manager != null) {
            manager.createNotificationChannel(channel);
        }
    }

    private Notification createNotification() {
        PendingIntent startPi = PendingIntent.getActivity(this, 1001,
                new Intent(this, MainActivity.class), PendingIntent.FLAG_IMMUTABLE);
        return new Notification.Builder(this, CHANNEL_ID)
                .setContentIntent(startPi)
                .setContentTitle(getString(R.string.notification_title))
                .setContentText(getActiveLogsText())
                .setSmallIcon(R.drawable.ic_launcher_foreground)
                .setOngoing(true)
                .build();
    }

    private void updateNotification() {
        NotificationManager manager = getSystemService(NotificationManager.class);
        if (manager != null) {
            manager.notify(NOTIFICATION_ID, createNotification());
        }
    }

    private String getActiveLogsText() {
        if (activeCollectors.size() == 0) return getString(R.string.all_log_stop);

        StringBuilder sb = new StringBuilder(getString(R.string.log_start, "")).append("：");
        for (int i = 0; i < activeCollectors.size(); i++) {
            int logType = activeCollectors.keyAt(i);
            sb.append(Utils.getLogTypeName(logType)).append(", ");
        }

        // Remove the last comma and space
        if (sb.length() > 2) {
            sb.setLength(sb.length() - 2);
        }

        return sb.toString();
    }

    private boolean shouldStartTimer() {
        return activeCollectors.size() == 1;
    }

    private boolean shouldStopTimer() {
        return activeCollectors.size() == 0;
    }

    private void checkAndCleanLogs() {
        synchronized (cleanupLock) {
            try {
                mProcessingFlag = true;
                File logRoot = new File(Utils.LOG_PATH);
                long totalSize = calculateFolderSize(logRoot);

                if (totalSize < Utils.MAX_TOTAL_LOG_SIZE) {
                    return;
                }

                // Find all log files (excluding active files)
                List<File> allLogFiles = new ArrayList<>();
                collectLogFiles(logRoot, allLogFiles);
                // Exclude the file currently being written
                allLogFiles.removeAll(getActiveLogFiles());

                //Sort by last modified time (oldest first)
                allLogFiles.sort(Comparator.comparingLong(File::lastModified));

                // Delete old files until size requirements are met
                for (File file : allLogFiles) {
                    if (totalSize < Utils.MAX_TOTAL_LOG_SIZE) {
                        break;
                    }

                    long fileSize = file.length();
                    if (file.delete()) {
                        totalSize -= fileSize;
                        // remove empty directories
                        deleteParentIfEmpty(file);
                    } else {
                        Log.e(TAG, "Failed to delete log file: " + file.getAbsolutePath());
                    }
                }
            } catch (Exception e) {
                Log.e(TAG, "Error during log cleanup: " + e.getMessage());
            } finally {
                mProcessingFlag = false;
            }
        }
    }

    // get all activity log files
    private Set<File> getActiveLogFiles() {
        Set<File> activeFiles = new HashSet<>();
        for (int i = 0; i < activeCollectors.size(); i++) {
            BaseLogCollector collector = activeCollectors.valueAt(i);
            if (collector.getCurrentLogFile() != null) {
                activeFiles.add(collector.getCurrentLogFile());
            }
        }
        return activeFiles;
    }

    // Find all log files
    private static void collectLogFiles(File dir, List<File> result) {
        if (dir.isDirectory()) {
            File[] files = dir.listFiles();
            if (files != null) {
                for (File file : files) {
                    if (file.isDirectory()) {
                        collectLogFiles(file, result);
                    } else if (file.getName().endsWith(".log")) {
                        result.add(file);
                    }
                }
            }
        }
    }

    // delete empty directories
    private static void deleteParentIfEmpty(File file) {
        File parent = file.getParentFile();
        while (parent != null && !parent.getAbsolutePath().equals(Utils.LOG_PATH)) {
            if (parent.list() == null || parent.list().length == 0) {
                parent.delete();
            } else {
                break;
            }
            parent = parent.getParentFile();
        }
    }

    // Recursive calculation of directory size
    private static long calculateFolderSize(File directory) {
        long length = 0;
        File[] files = directory.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isFile()) {
                    length += file.length();
                } else {
                    length += calculateFolderSize(file);
                }
            }
        }
        return length;
    }


    @SuppressLint("ApplySharedPref")
    private void startTimer() {
        long startTime = sharedPreferences.getLong(Utils.KEY_BEGIN_RECORDING_TIME, 0L);
        long lastRecordTime = sharedPreferences.getLong(Utils.KEY_LAST_RECORD_TIME, System.currentTimeMillis());
        long shutdownTime = System.currentTimeMillis() - lastRecordTime;

        // If there is no start time, set a new start time
        if (startTime == 0) {
            startTime = System.currentTimeMillis();

            // Save start time for recovery after restart
            if (sharedPreferences != null) {
                sharedPreferences.edit()
                        .putLong(Utils.KEY_BEGIN_RECORDING_TIME, startTime)
                        .commit();
            }
        }

        if (mTimer != null) {
            mTimer.cancel();
            mTimer = null;
        }
        mTimer = new Timer(true);
        long finalStartTime = startTime;
        mTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                long currentTime = System.currentTimeMillis();
                mTimeSecond = (currentTime - finalStartTime - shutdownTime) / 1000;
                notifyCallbacks();
                if (Utils.DEBUG) {
                    Log.d(TAG, "mTimer run: mTimeSecond = " + calculateTimer());
                }

                mCount++;
                if (mCount >= SAMPLING_FRAME_COUNT && !mProcessingFlag) {
                    new Thread(() -> checkAndCleanLogs()).start();
                    mCount = 0;
                }
            }
        }, 0, 1000);
    }

    @SuppressLint("ApplySharedPref")
    private void stopTimer() {
        if (mTimer != null) {
            mTimer.cancel();
            mTimer = null;
        }

        // Clear the saved time
        if (sharedPreferences != null) {
            sharedPreferences.edit()
                    .remove(Utils.KEY_BEGIN_RECORDING_TIME)
                    .remove(Utils.KEY_LAST_RECORD_TIME)
                    .commit();
        }
    }

    private String calculateTimer() {
        if (mTimeSecond < 0) {
            mTimeSecond = 0;
        }
        int hours = (int) (mTimeSecond / 3600);
        int minutes = (int) ((mTimeSecond % 3600) / 60);
        int seconds = (int) (mTimeSecond % 60);
        return String.format(Locale.getDefault(), "%02d:%02d:%02d", hours, minutes, seconds);
    }

    private void notifyCallbacks() {
        if (mTimerCallback != null) {
            mTimerCallback.onTimerUpdated(calculateTimer());
        }
    }

    public void registerTimerCallback(TimerCallback callback) {
        if (Utils.DEBUG) {
            Log.d(TAG, "registerTimerCallback: callback = " + callback);
        }
        mTimerCallback = callback;
    }

    // Define timing update callback interface
    public interface TimerCallback {
        void onTimerUpdated(String formattedTime);
    }
}